﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NUnit.Framework;
using XcoAppSpaces.Transport.Sockets.Auth;
using XcoAppSpaces.Transport.Sockets.Auth.Contract;
using System.Threading.Tasks;
using System.Threading;

namespace test.XcoAppSpaces.Transport.Sockets.Auth
{
	[TestFixture]
	public class AuthenticatorIntegrationTests
	{
		private Authenticator authenticator;

		[SetUp]
		public void SetUp()
		{
			authenticator = Authenticator.CreateDefaultInstance();
		}

		[Test]
		[Explicit]
		public void Test_EBC_Composition()
		{
			var authenticator = Authenticator.CreateDefaultInstance();
			authenticator.Inject(new SecuritySettings());
			authenticator.Out_AuthenticatedConnection += _ => Console.WriteLine("Result: AuthenticatedConnection");
			authenticator.Out_InvalidConnection += _ => Console.WriteLine("Result: InvalidConnection");

			authenticator.In_Connection(null);
		}


		[Test]
		public void ClientServerHandshakeInteraction_Successful()
		{
			var serverSettings = new SecuritySettings { AuthenticationKey = "test", ChallengeLength = 10, Greeting = "hello", SecondsAfterInvalidConnectionIsClosed = 5 };
			authenticator.Inject(serverSettings);
			var clientSettings = new SecuritySettings { AuthenticationKey = "test", ChallengeLength = 10, Greeting = "hello", SecondsAfterInvalidConnectionIsClosed = 5 };
			ClientServerHandshakeInteraction(serverSettings, clientSettings, true);
		}

		[Test]
		public void ClientServerHandshakeInteraction_Successful_LongKey()
		{
			var serverSettings = new SecuritySettings { AuthenticationKey = "test1234567890", ChallengeLength = 10, Greeting = "hello", SecondsAfterInvalidConnectionIsClosed = 5 };
			authenticator.Inject(serverSettings);
			var clientSettings = new SecuritySettings { AuthenticationKey = "test1234567890", ChallengeLength = 10, Greeting = "hello", SecondsAfterInvalidConnectionIsClosed = 5 };
			ClientServerHandshakeInteraction(serverSettings, clientSettings, true);
		}

		[Test]
		public void ClientServerHandshakeInteraction_Fail_DifferentKey()
		{
			var serverSettings = new SecuritySettings { AuthenticationKey = "test", ChallengeLength = 10, Greeting = "hello", SecondsAfterInvalidConnectionIsClosed = 5 };
			authenticator.Inject(serverSettings);
			var clientSettings = new SecuritySettings { AuthenticationKey = "1234", ChallengeLength = 10, Greeting = "hello", SecondsAfterInvalidConnectionIsClosed = 5 };
			ClientServerHandshakeInteraction(serverSettings, clientSettings, false);
		}

		[Test]
		public void ClientServerHandshakeInteraction_Fail_AddedToBlacklist_ThenFailsAgain()
		{
			var serverSettings = new SecuritySettings { AuthenticationKey = "test", ChallengeLength = 10, Greeting = "hello", SecondsAfterInvalidConnectionIsClosed = 5, NumInvalidAttemptsForBlacklist = 1 };
			authenticator.Inject(serverSettings);
			var clientSettings = new SecuritySettings { AuthenticationKey = "1234", ChallengeLength = 10, Greeting = "hello", SecondsAfterInvalidConnectionIsClosed = 5 };
			ClientServerHandshakeInteraction(serverSettings, clientSettings, false);

			clientSettings.AuthenticationKey = "test";
			ClientServerHandshakeInteraction(serverSettings, clientSettings, false);
		}

		[Test]
		public void ClientServerHandshakeInteraction_Fail_NotAddedToBlacklist_ThenSuccess()
		{
			var serverSettings = new SecuritySettings { AuthenticationKey = "test", ChallengeLength = 10, Greeting = "hello", SecondsAfterInvalidConnectionIsClosed = 5, NumInvalidAttemptsForBlacklist = 2 };
			authenticator.Inject(serverSettings);
			var clientSettings = new SecuritySettings { AuthenticationKey = "1234", ChallengeLength = 10, Greeting = "hello", SecondsAfterInvalidConnectionIsClosed = 5 };
			ClientServerHandshakeInteraction(serverSettings, clientSettings, false);

			clientSettings.AuthenticationKey = "test";
			ClientServerHandshakeInteraction(serverSettings, clientSettings, true);
		}
		
		public void ClientServerHandshakeInteraction(SecuritySettings serverSettings, SecuritySettings clientSettings, bool shouldBeValid)
		{
			using (var connections = new ConnectedTcpClientPair())
			{
				var clientHandshake = new DoClientHandshake(new PrepareAuthentication(), new SendGreeting(), new ReceiveChallenge(), new SendResponse(), new FinishAuthentication());
				clientHandshake.Inject(clientSettings);

				bool serverValid = false;
				bool serverInvalid = false;
				bool clientValid = false;
				bool clientInvalid = false;

				authenticator.Out_AuthenticatedConnection += _ => serverValid = true; //Console.WriteLine("Server connection valid!");
				authenticator.Out_InvalidConnection += _ => serverInvalid = true; //Console.WriteLine("Server connection invalid!");
				clientHandshake.Out_AuthenticatedConnection += _ => clientValid = true; //Console.WriteLine("Client connection valid!");
				clientHandshake.Out_InvalidConnection += _ => clientInvalid = true; //Console.WriteLine("Client connection invalid!");

				Parallel.Invoke(
					() => clientHandshake.In_Connection(connections.SendingClient),
					() => authenticator.In_Connection(connections.ReceivingClient));

				Assert.IsTrue(SpinWait.SpinUntil(() => (serverValid && clientValid) || (serverInvalid && clientInvalid), 10000));
				if (shouldBeValid)
					Assert.IsTrue(serverValid && clientValid, "Server and client should be valid!");
				else
					Assert.IsTrue(serverInvalid && clientInvalid, "Server and client should be invalid!");
				
			}
		}
	}
}
